
// ...hack for IE to be able to pass parameters into a delayed function call.
// http://webreflection.blogspot.com/2007/06/simple-settimeout-setinterval-extra.html
{
/*@cc_on
(function(f){
 window.setTimeout =f(window.setTimeout);
 window.setInterval =f(window.setInterval);
})(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c.apply(this,a)},t)}});
@*/
}

function Browser() {
  var ua = navigator.userAgent.toLowerCase();
  this.isStrict = document.compatMode == "CSS1Compat";
  this.isOpera = ua.indexOf("opera") > -1;
  this.isSafari = (/webkit|khtml/).test(ua);
  this.isIE = !this.isOpera && ua.indexOf("msie") > -1;
  this.isIE7 = !this.isOpera && ua.indexOf("msie 7") > -1;
  this.isGecko = !this.isSafari && ua.indexOf("gecko") > -1;
  this.isBorderBox = this.isIE && !this.isStrict;
  this.isWindows = (ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1);
  this.isMac = (ua.indexOf("macintosh") != -1 || ua.indexOf("mac os x") != -1);
  this.isLinux = (ua.indexOf("linux") != -1);
  this.isSecure = window.location.href.toLowerCase().indexOf("https") == 0;
}
browser = new Browser();

if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function(obj) {
		for ( var i = 0; i < this.length; ++i) {
			if (this[i] == obj)
				return i;
		}
		return -1;
	}
}

// inheritance
Object.extend = function(subClassName, baseClassName) {
	if (!Object._extensions)
		Object._extensions = new Object();
	Object._extensions[subClassName] = baseClassName;
}

// apply all registered extensions (see Object.extend()).
// To be called in an window.onload or after the scripts have been loaded (see
// for example ScriptSupport).
Object.applyExtensions = function() {
	if (!Object._extensions)
		return;
	var ancestors = new Array();
	for ( var s in Object._extensions) {
		var i = 0;
		ancestors[i++] = s;
		var a = s;
		while (a = Object._extensions[a])
			ancestors[i++] = a;
		// FF 3 seems to have problems with "--i"
		var baseClass = eval(ancestors[i - 1]);
		if (typeof (baseClass) != "function")
			throw new Error("Cannot create class " + ancestors[i - 2] + ": Base class " + ancestors[i - 1]
					+ " not available!");
		for (i = i - 2; i >= 0; i--) {
			var subClass = eval(ancestors[i]);
			if (typeof (subClass) != "function")
				throw new Error("Cannot create class " + ancestors[i]
						+ ": No constructor function available!");
			if (!subClass._super) {
				// actual extensions
				var _inh = function() {};
				_inh.prototype = baseClass.prototype;
				var existingProto = subClass.prototype;
				subClass.prototype = new _inh();
				subClass.prototype.constructor = subClass;
				subClass._super = baseClass;
				for ( var m in existingProto)
					subClass.prototype[m] = existingProto[m];
			}
			baseClass = subClass;
		}
	}
}

// event
function EventListener(obj, type, fn, context) {
	this.obj = obj;
	this.type = type;
	this.fn = fn;
	this.context = context;
	if (!document._eventListeners)
		document._eventListeners = new Array();
	this.idx = document._eventListeners.length;
	document._eventListeners[this.idx] = this;
	if (document.addEventListener) {
		this._fn = this.fn;
		if (this.context) {
			var self = this;
			this._fn = function(evn) {
				self.fn.call(self.context, evn);
			};
		}
		this.obj.addEventListener(this.type, this._fn, false);
	}
	else {
		if (!this.context)
			this.context = this.obj;
		this.obj['__e_' + this.idx] = this.fn;
		var self = this;
		this._handler = function(evn) {
			evn.target = evn.srcElement;
			if (!evn.stopPropagation) {
				evn.stopPropagation = function() {
					this.cancelBubble = true;
				};
			}
			evn.returnValue = self.obj['__e_' + self.idx].call(self.context, evn);
			return evn.returnValue;
		}
		this.obj.attachEvent('on' + this.type, this._handler);
	}
}

EventListener.prototype.remove = function() {
	if (document.addEventListener) {
		this.obj.removeEventListener(this.type, this._fn, false);
		this._fn = null;
	}
	else {
		this.obj.detachEvent('on' + this.type, this._handler);
		this._handler = null;
		this.obj['__e_' + this.idx] = null;
	}
	document._eventListeners[this.idx] = null;
}

EventListener.removeListener = function(obj, type, fn) {
	if (!document._eventListeners)
		return;
	for ( var i = 0; i < document._eventListeners.length; ++i) {
		var l = document._eventListeners[i];
		if (l && (l.obj == obj) && (l.type == type) && (l.fn == fn))
			l.remove();
	}
}

EventListener.setupAutoCleanup = function() {
	if (!window._cleanupEventListener) {
		window._cleanupEventListener = new EventListener(window, "unload", function(evn) {
			if (document._eventListeners) {
				for ( var i = 0; i < document._eventListeners.length; ++i) {
					var l = document._eventListeners[i];
					l && l.remove();
				}
			}
		});
	}
}

EventListener.cancelEvent = function(evn) {
	evn.preventDefault && evn.preventDefault();
	evn.stopPropagation && evn.stopPropagation();
	evn.cancelBubble = true;
	return false;
}

if (!window.console) {
	// console dummy of IE
	window.console = new Object();
	window.console.log = function(msg) {
		// alert(msg);
	}
}

/**
 * @class Client Cookie to store a string value.
 * @param {string}
 *          name The name of the cookie.
 * @constructor
 */
function Cookie(name) {
	this.name = name;
}

Cookie.prototype.getValue = function() {
	var nameEQ = this.name + "=";
	var ca = document.cookie.split(';');
	for ( var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while (c.charAt(0) == ' ')
			c = c.substring(1, c.length);
		if (c.indexOf(nameEQ) == 0)
			return c.substring(nameEQ.length, c.length);
	}
	return null;
}

/**
 * Set the cookie value, valid for a certain time.
 * 
 * @param {string}
 *          value The value to store.
 * @param {int}
 *          days The number of days for the cookie value to be valid. Use -1 to
 *          delete the cookie.
 */
Cookie.prototype.setValue = function(value, days) {
	var expires;
	if (days) {
		var date = new Date();
		date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
		expires = "; expires=" + date.toGMTString();
	}
	else
		expires = "";
	var path = BasePath.get();
	var protocolPos = path.indexOf("://");
	if (protocolPos >= 0) {
		path = path.substring(protocolPos + 3);
		var domainPos = path.indexOf("/");
		if (domainPos >= 0)
			path = path.substring(domainPos);
		else
			path = "/";
	}
	if (path.charAt(0) != '/')
		path = "/" + path;
	document.cookie = this.name + "=" + value + expires + "; path=" + path;
}

// JavaScript
function ScriptSupport(doc) {
	this.doc = doc;
	this._init();
}

ScriptSupport.prototype.loadScript = function(url) {
	url = BasePath.get() + EDIT_SCRIPT_BASE + "/" + url;
	var head = this.doc.getElementsByTagName("head")[0];
	var script = this.doc.createElement("script");
	script.setAttribute("type", "text/javascript");
	script.setAttribute("src", url);
	this.numLoadingScripts++;
	head.appendChild(script);
	script._support = this;
	script.onload = function() {
		if (!this.readyState || (this.readyState == "loaded") || (this.readyState == "complete"))
			this._support.onScriptLoaded();
	};
	script.onreadystatechange = script.onload;
	return script;
}

ScriptSupport.prototype.onScriptLoaded = function() {
	this.numLoadingScripts--;
	if (this.readyToInit && (1 > this.numLoadingScripts)) {
		// call this._init() directly => page load progress does not stop in FF 2
		var self = this;
		window.setTimeout( function() {
			self._init();
		}, 1);
	}
}

ScriptSupport.prototype._init = function() {
	Object.applyExtensions();
	if (this.inits) {
		for ( var i = 0; i < this.inits.length; ++i)
			this.inits[i]();
	}
	// reset to reuse
	this.inits = new Array();
	this.numLoadingScripts = 0;
	this.readyToInit = false;
}

ScriptSupport.prototype.init = function() {
	this.readyToInit = true;
	if (1 > this.numLoadingScripts)
		this._init();
}

ScriptSupport.prototype.addInit = function(fnInit) {
	this.inits[this.inits.length] = fnInit;
}

scriptSupport = new ScriptSupport(document);

// resources
function ResourceBundle(statusDisplay) {
	this._pendingRequests = 0;
	this.statusDisplay = statusDisplay;
}

// Add a resource to this bundle.
// name string Name of the new resource.
// url string URL to retrieve the resource data.
ResourceBundle.prototype.add = function(name, url) {
	this[name] = new Resource(url, name, this.statusDisplay, this);
	this._pendingRequests++;
};

// Load all the resources of this bundle.
ResourceBundle.prototype.load = function() {
	for ( var prop in this) {
		var p = this[prop];
		p && p.load && p.load();
	}
};

// Callback after all the resources are loaded.
ResourceBundle.prototype.onCompleted = function() {
};

// Callback after the state of one resource has changed.
ResourceBundle.prototype.onChanged = function(resource) {
};

// Callback after one resource has failed to load.
ResourceBundle.prototype.onFailed = function(resource) {
};

// One resource optionally managed by a bundle.
// @url URL to locate the resource.
// @name Optional name of the resource (mainly to be managed by a bundle).
// @resourceBundle Optional resource bundle to manage this new resource.
function Resource(url, name, statusDisplay, resourceBundle) {
	this.bundle = resourceBundle;
	this.name = name;
	this.url = url;
	this.statusDisplay = statusDisplay;
	if (!statusDisplay && (typeof StatusDisplay != "undefined") && StatusDisplay.getInstance)
		this.statusDisplay = StatusDisplay.getInstance();
}

// Build or rebuild the XMLHttpRequest for this resource.
Resource.prototype._buildRequest = function() {
	this.text = null;
	this.dom = null;
	try {
		this.request = new XMLHttpRequest();
	}
	catch (trymicrosoft) {
		try {
			this.request = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (othermicrosoft) {
			try {
				this.request = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (failed) {
				throw "failed to create XMLHttpRequest!";
			}
		}
	}
	var self = this;
	this.request.onreadystatechange = function() {
		self.onStateChange();
	};
}

Resource.prototype.onStateChange = function() {
	if (this.request.readyState == 4) {
		try {
			if (this.request.status == 200) {
				this.text = this.request.responseText;
				this.dom = this.request.responseXML;
			}
			this.statusText = this.request.statusText;
		}
		catch (ex) {
			this.failed = true;
			this.statusText = (ex && ex.message) ? ex.message : "Connection error!";
		}
	}
	else {
		this.statusText = "Transfer not complete yet (state: " + this.request.readyState + ").";
	}
	if (this.failed) {
		this.statusDisplay && this.statusDisplay.showError
				&& this.statusDisplay.showError(this.statusText);
		if (this.onFailed)
			this.onFailed();
	}
	else if (this.text) {
		this.statusDisplay && this.statusDisplay.show && this.statusDisplay.show(this.statusText);
		if (this.onCompleted)
			this.onCompleted();
	}
	else {
		this.statusDisplay && this.statusDisplay.show && this.statusDisplay.show(this.statusText);
		if (this.onChanged)
			this.onChanged();
	}
	if (this.bundle) {
		this.text && this.bundle._pendingRequests--;
		if (this.failed)
			this.bundle.onFailed && this.bundle.onFailed(this);
		else if (this.bundle._pendingRequests < 1)
			this.bundle.onCompleted && this.bundle.onCompleted();
		else
			this.bundle.onChanged && this.bundle.onChanged(this);
	}
};

Resource.prototype.load = function() {
	try {
		this._buildRequest();
		this.statusText = "loading...";
		this.request.open("GET", this.url, true);
		this.request.send(null);
	}
	catch (ex) {
		this.failed = true;
		this.statusText = (ex && ex.message) ? ex.message : "Connection error!";
		this.statusDisplay && this.statusDisplay.showError
				&& this.statusDisplay.showError(this.statusText);
		if (this.onFailed)
			this.onFailed();
	}
};

Resource.prototype.send = function(data) {
	try {
		this._buildRequest();
		this.statusText = "sending...";
		this.request.open("POST", this.url, true);
		this.request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		this.request.send(data);
	}
	catch (ex) {
		this.failed = true;
		this.statusText = (ex && ex.message) ? ex.message : "Connection error!";
		this.statusDisplay && this.statusDisplay.showError
				&& this.statusDisplay.showError(this.statusText);
		if (this.onFailed)
			this.onFailed();
	}
};

// Transforms this resource into a container element.
// stylesheet Resource The stylesheet resource with the XSLT DOM.
// resultContainer HTMLElement Container element to hold the transformation
// result.
Resource.prototype.transform = function(stylesheet, resultContainer) {
	if (!stylesheet || !stylesheet.dom)
		// TODO: exception handling...
		return;
	Resource.transformNode(this.dom, stylesheet.dom, resultContainer);
};

Resource.transformNode = function(dom, stylesheetDom, resultContainer) {
	if (!dom || !stylesheetDom)
		// TODO: exception handling...
		return;
	if (typeof XSLTProcessor != "undefined") {
		if (!stylesheetDom._processor) {
			stylesheetDom._processor = new XSLTProcessor();
			stylesheetDom._processor.importStylesheet(stylesheetDom);
		}
		var d = stylesheetDom._processor.transformToFragment(dom, resultContainer.ownerDocument);
		resultContainer.innerHTML = "";
		resultContainer.appendChild(d);
	}
	else {
		resultContainer.innerHTML = dom.transformNode(stylesheetDom);
	}
};

Resource.parseJsArray = function(text) {
	if ((text.indexOf("[") != 0) || (text.lastIndexOf("]") != (text.length - 1)))
		return null;
	eval("var v = " + text);
	return v;
};

// Callback after this resource has been completely loaded.
Resource.prototype.onCompleted = function() {
};

// Callback after the state of this resource has changed.
Resource.prototype.onChanged = function() {
};

// Callback after this resource has failed to load.
Resource.prototype.onFailed = function() {
};

// Loading this resouce has completed (or failed).
Resource.prototype.hasCompleted = function() {
	return (null != this.text);
};

function LivCosResource(url, name, statusDisplay, resourceBundle) {
	LivCosResource._super.call(this, url, name, statusDisplay, resourceBundle);
	this.errorMessage = "Exception!"
}
Object.extend("LivCosResource", "Resource");

LivCosResource.prototype.onCompleted = function() {
	if (this.dom && this.dom.documentElement) {
		var root = this.dom.documentElement;
		if (0 == root.nodeName.indexOf("job_")) {
			if ("FAILED" == root.getAttribute("state")) {
				// job exception
				var msg = this.errorMessage + " " + root.textContent;
				this.statusDisplay.showError(msg, "/livcos.org/web/meta?ref=/localhost/system/work/"
						+ root.nodeName);
			}
			else {
				// job status
				this.statusDisplay.show("retry...");
				this.send("_job=" + root.nodeName);
			}
			return;
		}
	}
	this.onSuccess && this.onSuccess();
}

LivCosResource.prototype.onFailed = function() {
	this.statusDisplay.showError(this.errorMessage + " " + this.statusText);
}

function Job(url, statusDisplay) {
	Job._super.call(this, url, null, statusDisplay);
	this.data = "";
}
Object.extend("Job", "LivCosResource");

Job.prototype.addData = function(key, value) {
	if (value) {
		if (this.data != "")
			this.data += "&";
		this.data += key + "=" + encodeURIComponent(value);
	}
}

Job.prototype.addFormData = function(form) {
	for ( var i = 0; i < form.elements.length; ++i) {
		var e = form.elements[i];
		if ((e.type == "submit") || (e.type == "button") || (e.type == "image"))
			continue;
		this.addData(e.name, e.value);
	}
}

Job.prototype.start = function() {
	this.send(this.data);
}

// Date extensions
Date.prototype.toXsTimeString = function() {
	return this.format("HH:mm:ss");
}

Date.prototype.toXsDateString = function() {
	return this.format("yyyy-MM-dd");
}

Date.prototype.toXsDateTimeString = function() {
	return this.format("yyyy-MM-ddTHH:mm:ss");
}

Date.prototype.format = function(pattern) {
	var d = this;
	return pattern.replace(/(yyyy|MM|dd|HH|mm|ss)/gi, function($1) {
		var r;
		switch ($1) {
		case 'yyyy':
			return d.getFullYear();
		case 'MM':
			r = "0" + (d.getMonth() + 1);
			break;
		case 'dd':
			r = "0" + d.getDate();
			break;
		case 'HH':
			r = "0" + d.getHours();
			break;
		case 'mm':
			r = "0" + d.getMinutes();
			break;
		case 'ss':
			r = "0" + d.getSeconds();
			break;
		}
		return r.substr(r.length - 2, 2);
	});
}

// copy from jscalendar 1.0 http://www.dynarch.com/demos/jscalendar
/** Returns the number of the week in year, as defined in ISO 8601. */
Date.prototype.getWeekNumber = function() {
	var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var DoW = d.getDay();
	d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
	var ms = d.valueOf(); // GMT
	d.setMonth(0);
	d.setDate(4); // Thu in Week 1
	return Math.round((Number(ms) - Number(d.valueOf())) / (7 * 864e5)) + 1;
};

Date.ONE_DAY = 24 * 3600 * 1000;
Date.DAY_NAMES = new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
		"Saturday");

Date.MONTH_NAMES = new Array("January", "February", "March", "April", "May", "June", "July",
		"August", "September", "October", "November", "December");

Date.prototype.getDayName = function() {
	return Date.DAY_NAMES[this.getUTCDay()];
};

Date.prototype.getMonthName = function() {
	return Date.MONTH_NAMES[this.getMonth()];
};

Date.prototype.setTimePart = function(time) {
	this.setHours(time.getHours());
	this.setMinutes(time.getMinutes());
	this.setSeconds(time.getSeconds());
};

Date.parseXsDate = function(dateString) {
	var tockens = dateString.split("-");
	if (3 != tockens.length)
		return null;
	return new Date(Number(tockens[0]), Number(tockens[1]) - 1, Number(tockens[2]));
};

Date.parseXsTime = function(timeString) {
	var pos = timeString.indexOf(".");
	if (pos > 0)
		timeString = timeString.substring(0, pos);
	return new Date("1/1/2000 " + timeString);
};

Date.parseXsDateTime = function(dateTimeString) {
	var tockens = dateTimeString.split("T");
	var date = Date.parseXsDate(tockens[0]);
	date.setTimePart(Date.parseXsTime(tockens[1]));
	return date;
};

function Uri(urlString) {
	this.url = urlString;
}

// builds the absolute URL for this URL, based on the current documents
// location.
Uri.prototype.getAbsoluteUri = function() {
	var url = this.url;
	if (url.indexOf("://") > 0)
		return url;
	var tks = url.split("/");
	var loc_tks = window.location.href.split("/");
	var p = loc_tks.length - 1;
	for (var i = 0; i < tks.length; ++i) {
		var t = tks[i];
		if ("." == t)
			continue;
		if (".." == t)
			p--;
		else
			loc_tks[p++] = t;
	}
	url = loc_tks[0] + "/";
	for (var i = 2; i < p; ++i) {
		url += "/" + loc_tks[i];
	}
	return url;
}

Uri.prototype.getPath = function() {
	var p = this.url;
	if (p.indexOf("://") > 0)
		p = p.replace("^[^:]+://[^/]+", "");
	var i = p.indexOf("#");
	if (i > 0)
		p = p.substring(0, i);
	i = p.indexOf("?");
	if (i > 0)
		p = p.substring(0, i);
	return p;
}

Uri.prototype.getFragment = function() {
	var p = this.url;
	var i = p.indexOf("#");
	if (i > 0)
		p = p.substring(i + 1);
	else
		return "";
	i = p.indexOf("?");
	if (i > 0)
		p = p.substring(0, i);
	return p;
}

// The base path to the Cosmos root. It depends on the deployment, some
// examples:
// LivCos at the server's root: BasePath.get() = ""
// LivCos in the "livcos" context: BasePath.get() = "/livcos"
// It will be determined automatically in the page onload handler (at the end of
// this file).
// This path allows to form absolute paths independent from the deployment.
function BasePath() {
}

BasePath.get = function() {
	if (BasePath._path)
		return BasePath._path;
	var SCRIPT_BASE = "/livcos.org/web/script";
	BasePath.setByScript(SCRIPT_BASE + "/util.js");
	if (BasePath._path)
		return BasePath._path;
	BasePath.setByScript(SCRIPT_BASE + "/scripts.js");
	return BasePath._path;
}

BasePath.setByScript = function(scriptPath) {
	if (BasePath._path)
		return BasePath._path;
	var scripts = document.getElementsByTagName("script");
	for ( var i = 0; i < scripts.length; ++i) {
		var uri = (new Uri(scripts[i].src)).getAbsoluteUri();
		var pos = (uri + "_MARKER2COMPARE").indexOf(scriptPath + "_MARKER2COMPARE");
		if (pos > 0) {
			BasePath._path = uri.substring(0, pos);
			return BasePath._path;
		}
	}
	return BasePath._path;
}

BasePath.setByDoc = function(doc, docPath) {
	if (BasePath._path)
		return BasePath._path;
	var uri = doc.location.href;
	var pos = uri.indexOf("?");
	if (pos > 0)
		uri = uri.substring(0, pos);
	pos = uri.indexOf("#");
	if (pos > 0)
		uri = uri.substring(0, pos);
	pos = (uri + "_MARKER2COMPARE").indexOf(docPath + "_MARKER2COMPARE");
	if (pos > 0) {
		BasePath._path = uri.substring(0, pos);
		return BasePath._path;
	}
	return BasePath._path;
}

new EventListener(window, "load", function(evn) {
	// apply the extensions known so far
		Object.applyExtensions();
		// setup auto cleanup of all the registered event listeners
		EventListener.setupAutoCleanup();
	});
